﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Net;

namespace DownloadApi.Net
{
    public class DownloadItem
    {
        // Link to app data
        public string App_Id;
        public string SelectedVersionIndex;

        // ESSENTIAL details for actual download
        public string DownloadUrl;
        public string FilePath;
        public int StartPoint;

        public DownloadStatus Status=DownloadStatus.PENDING;
        public enum DownloadStatus
        {
            PENDING,
            COMPLETED,
            FAILED,
            URLNOTFOUND,
            UNABLETOWRITE,
            CANCELLED
        }
    }

    public class DownloadManager
    {
        public string BaseDownloadPath;
        
        // workers and items to download
        DownloadWorker[] _Workers;
        private DownloadItem[] _DownloadList;

        // any workers working
        private static bool _AnyWorkersWorking = false;
        public static bool AnyWorkersWorking
        {
            get { return _AnyWorkersWorking; }
        }

        // Total number of threads (backgroundworkers) to create
        private int _NumThreads;
        private int _iNumJobs;
        private int _iNumJobsDone;
        private int _LastSentThread;
        private Form FormManager;

        public int NumThreads
        {
            get { return _NumThreads; }
            set { _NumThreads = value; }
        }
        public int NumJobs
        {
            get { return _iNumJobs; }
            set { _iNumJobs = value; }
        }
        public int NumJobsDone
        {
            get { return _iNumJobsDone; }
            set { _iNumJobsDone = value; }
        }

        private List<string> _LogList = new List<string>();
        public List<string> LogList
        {
            get { return _LogList; }
            set { _LogList = value; }
        }

        /// <summary>
        /// Add string to log
        /// </summary>
        /// <param name="logstring"></param>
        public void AddLog(string logstring)
        {
            logstring = DateTime.Now.ToString("dd-MM-yy") + " : " + logstring; 
            _LogList.Add(logstring);

        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="tForm">Parent form reference</param>
        /// <param name="iNumThreads"></param>
        /// <param name="downloadlist"></param>
        public DownloadManager(Form tForm, int iNumThreads, DownloadItem[] downloadlist)
        {
            FormManager = tForm;
            _NumThreads = iNumThreads;
            _LastSentThread = -1;
            _DownloadList = downloadlist;
        }

        public void LaunchManagedProcess()
        {
            // set cancel to true
            Button BtnStart = (Button)FormManager.Controls["panel1"].Controls["btnStart"];
            Button BtnCancel = (Button)FormManager.Controls["panel1"].Controls["btnCancel"];
            BtnStart.Enabled = false;
            BtnCancel.Enabled = true;          
            InitManager();
            AssignWorkers();
        }
        public void StopManagedProcess()
        {
            foreach (DownloadWorker LW in _Workers)
            {
                if ((LW.IsBusy == true) || (LW.CancellationPending == false))
                    LW.CancelAsync();
            }

            // set cancel to true
            Button BtnStart = (Button)FormManager.Controls["panel1"].Controls["btnStart"];
            Button BtnCancel = (Button)FormManager.Controls["panel1"].Controls["btnCancel"];
            BtnStart.Enabled = true;
            BtnCancel.Enabled = false;
        }


        /// <summary>
        /// Initialize Manager Data
        /// </summary>
        public void InitManager()
        {                              
            _LastSentThread = -1;
            _iNumJobs = _DownloadList.Length;
            _iNumJobsDone = 0;
            
            _Workers = new DownloadWorker[_NumThreads];

            for (int i = 0; i < _NumThreads; i++)
            {
                _Workers[i] = new DownloadWorker(i);
                ConfigureWorker(_Workers[i]);
            }
        }

        private void ConfigureWorker(DownloadWorker MTW)
        {
            //We associate the events of the worker
            MTW.ProgressChanged += DLWorker_ProgressChanged;
            MTW.RunWorkerCompleted += MTWorker_RunWorkerCompleted;
        }

        private void DLWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            DownloadWorker LWorker = sender as DownloadWorker;
            DownloadWorker.ProgressUserState prgState = (DownloadWorker.ProgressUserState)e.UserState;
            ListView lv = (ListView)FormManager.Controls["listView1"];
            lv.Items[LWorker.JobId].SubItems[3].Text = e.ProgressPercentage.ToString() + "%";
        }

        private void MTWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            String PBName;
            String lbl_Name;
            DownloadWorker LWorker = sender as DownloadWorker;
            String result = "";
            int LJobId = LWorker.JobId;

            LWorker.JobId = 0; //Now the Worker is not doing any job...
            
    
            // First, handle the case where an exception was thrown.
            if (e.Error != null)
            {
                // MessageBox.Show(this._DownloadList[_LastSentThread].DownloadUrl + "\r\n"+ e.Error.Message);
                
                // update status
                if (e.Error.Message.Contains("404"))
                {
                    // url not found
                    this._DownloadList[LJobId].Status = DownloadItem.DownloadStatus.URLNOTFOUND;
                }
                else
                {
                    // other error
                    this._DownloadList[LJobId].Status = DownloadItem.DownloadStatus.FAILED;
                }
                ListView lv = (ListView)FormManager.Controls["listView1"];
                lv.Items[LJobId].SubItems[3].Text = "ERROR:" + e.Error.Message;
                // lv.Items[LJobId].SubItems[3].Text = this._DownloadList[LJobId].Status.ToString();

                AssignWorkers();
                return;
            }
            else if (e.Cancelled)
            {
                // Next, handle the case where the user canceled 
                // the operation.
                // Note that due to a race condition in 
                // the DoWork event handler, the Cancelled
                // flag may not have been set, even though
                // CancelAsync was called.
                this._DownloadList[LJobId].Status = DownloadItem.DownloadStatus.CANCELLED;

                ListView  lv = (ListView)FormManager.Controls["listView1"];
                // lv.Items[LJobId].SubItems[1].Text = "Cancelled";
                lv.Items[LJobId].SubItems[3].Text = this._DownloadList[LJobId].Status.ToString();
                
                return;
            }
            else
            {
                // Finally, handle the case where the operation 
                // succeeded.
                _iNumJobsDone = _iNumJobsDone + 1; //We control here the jobs done.
                _DownloadList[LJobId].Status = DownloadItem.DownloadStatus.COMPLETED;
                result = (String)e.Result;
            }
             
            AssignWorkers();
        }


        /// <summary>
        /// Check out workers
        /// </summary>
        public void AssignWorkers()
        {
            Boolean ThereAreWorkersWorking = false;
            //We check all workers that are not doing a job... and assign a new one
            
            foreach (DownloadWorker W in _Workers)
            {
                if (W.IsBusy == false)
                {
                    //If there are still jobs to be done... we assign the job to the free worker
                    if (_iNumJobs-1 > _LastSentThread)
                    {
                        _LastSentThread = _LastSentThread + 1; //We control the threads associated to a worker (not meaning the jobs done) just 4 control.
                        W.JobId = _LastSentThread; //We assign the job number..

                        DownloadItem di = this._DownloadList[_LastSentThread];

                        string filepath = BaseDownloadPath.TrimEnd(new char[] { '\\' }) + "\\" + di.FilePath;
                        string dirname = Path.GetDirectoryName(filepath);
                        // check path exists for this file
                        if (!Directory.Exists(dirname))
                        {
                            // create it
                            Directory.CreateDirectory(dirname);
                        }

                        // Set Progress to awaiting...
                        ListView lv = (ListView)FormManager.Controls["listView1"];
                        lv.Items[_LastSentThread].SubItems[3].Text = "Awaiting..";                        

                        // TODO: FIX DOWNLOAD PATH AND FILENAME
                        W.StartDownload(di.DownloadUrl, filepath, di.StartPoint);
                        
                        // W.RunWorkerAsync(); //We pass the parameters for the job.
                        ThereAreWorkersWorking = true; //We have at least this worker we just assigned the job working..
                    }
                }
                else
                {
                    ThereAreWorkersWorking = true;
                }
            }

            if (ThereAreWorkersWorking == false)
            {
                //This means that no worker is working and no job has been assigned. this means that the full package of jobs has finished
                //We could do something here...
                Button BtnStart = (Button)FormManager.Controls["panel1"].Controls["btnStart"];
                Button BtnCancel = (Button)FormManager.Controls["panel1"].Controls["btnCancel"]; 
                BtnStart.Enabled = true;
                BtnCancel.Enabled = false;
                
                // Build Log List
                foreach (DownloadItem di in _DownloadList)
                {
                    AddLog("[" + di.Status + "]" + di.DownloadUrl + "==>" + di.FilePath );
                }
                
           
                MessageBox.Show("All Download Jobs have finished, this is where they will need to be verified (MD5 hashes etc) to ensure correct download");
            }

            _AnyWorkersWorking = ThereAreWorkersWorking;
        }
    }

    public static class DownloadTools
    {
        /// <summary>
        /// Get filesize from a given dl url
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static int GetFileSize(String url)
        {
            try
            {
                Uri Url = new Uri(url);

                // Create a 'HttpWebrequest' object with the specified Uri.
                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(Url);

                // Send the 'HttpWebRequest' and wait for response.   
                HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();

                // Release resources of response object.
                myHttpWebResponse.Close();

                // return the content length og the file
                return (int)myHttpWebResponse.ContentLength;
            }
            catch (WebException e)
            {
                MessageBox.Show(String.Format("\r\nWebException thrown.The Reason for failure is : {0}", e.Status));
                return -1;
            }
            catch (Exception e)
            {
                MessageBox.Show(String.Format("\nThe following Exception was raised : {0}", e.Message));
                return -1;
            }
        }
    }
}
